/*
 * Copyright 2007 Gergely Kis
 * Copyright 2006 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.user.client.impl;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
import com.gwthtml.servlet.GWTContext;
import com.gwthtml.util.CSSParser;
import com.gwthtml.util.HTMLParser;

import java.io.StringReader;
import java.util.Iterator;
import java.util.List;

import org.jdom.Document;
import org.jdom.filter.ContentFilter;
import org.jdom.filter.ElementFilter;


/**
 * Native implementation associated with {@link com.google.gwt.user.client.DOM}.
 */
public abstract class DOMImpl {
  
  public void appendChild(Element parent, Element child) {
    ElementImpl p = (ElementImpl) parent;
    ElementImpl c = (ElementImpl) child;
    if (c.getParent() != null) {
    	c.getParent().removeContent(c);
    }
    p.addContent(c);
  }

  public abstract boolean compare(Element elem1, Element elem2);

  public Element createElement(String tag) {
	  ElementImpl elem = new ElementImpl(tag);
	  return (Element) elem;
  }

  public Element createInputElement(String type) {
	  ElementImpl e = new ElementImpl("input");
	  e.setAttribute("type", type);
	  return (Element) e;	  
  }

  public abstract Element createInputRadioElement(String group);

  
  public void eventCancelBubble(Event evt, boolean cancel) {
	  if (cancel) {
     ((EventImpl) evt).cancel(); 
    }
  }

  public boolean eventGetAltKey(Event evt) {
	  throw new RuntimeException("Not supported");
  }

  public int eventGetButton(Event evt) {
	  throw new RuntimeException("Not supported");
  }

  public int eventGetClientX(Event evt) {
	  throw new RuntimeException("Not supported");
  }

  public int eventGetClientY(Event evt) {
	  throw new RuntimeException("Not supported");
  }

  public boolean eventGetCtrlKey(Event evt) {
	  throw new RuntimeException("Not supported");
  }

  public Element eventGetFromElement(Event evt) {
    throw new RuntimeException("Not supported");
  }

  public int eventGetKeyCode(Event evt) {
	  throw new RuntimeException("Not supported");
  }

  public boolean eventGetRepeat(Event evt) {
	  throw new RuntimeException("Not supported");
  }

  public int eventGetScreenX(Event evt) {
	  throw new RuntimeException("Not supported");
  }
  
  public int eventGetScreenY(Event evt) {
	  throw new RuntimeException("Not supported");
  }

  public boolean eventGetShiftKey(Event evt) {
	  throw new RuntimeException("Not supported");
  }

  public abstract Element eventGetTarget(Event evt);

  public abstract Element eventGetToElement(Event evt);

  public String eventGetType(Event evt) {
    return ((EventImpl) evt).getTypeString();
  }

  public int eventGetTypeInt(Event evt) {
    return ((EventImpl) evt).getType();
  }
  
  public abstract void eventPreventDefault(Event evt);

  public void eventSetKeyCode(Event evt, char key) {
	  throw new RuntimeException("Not supported");
  }

  public abstract String eventToString(Event evt);

  public int getAbsoluteLeft(Element elem) {
	  throw new RuntimeException("Not supported");
  }
  
  public int getAbsoluteTop(Element elem) {
	  throw new RuntimeException("Not supported");
  }

  public String getAttribute(Element elem, String attr) {
	  ElementImpl e = (ElementImpl) elem;
	  return e.getAttributeValue(attr);
  }

  public boolean getBooleanAttribute(Element elem, String attr) {
	  return new Boolean(getAttribute(elem, attr)).booleanValue();
  }

  public abstract Element getChild(Element elem, int index);

  public abstract int getChildCount(Element elem);

  public abstract int getChildIndex(Element parent, Element child);

  public Element getElementById(String id) {
    Document doc = GWTContext.getDOMDocument();
    return (Element) findElementById(doc.getRootElement(), id);
  }

  private org.jdom.Element findElementById(org.jdom.Element current, String id) {
	String idValue = current.getAttributeValue("id");
	if (idValue != null) {
	  if (idValue.equals(id)) {
		return current;
	  }
	}
	
	List childList = current.getChildren();
	Iterator iter = childList.iterator();
	while (iter.hasNext()) {
		org.jdom.Element element = (org.jdom.Element) iter.next(); 
		org.jdom.Element found = findElementById(element, id);
		if (found != null) {
			return found;
		}
	}
	return null;
  }

public int getEventsSunk(Element elem) {
	  ElementImpl e = (ElementImpl) elem;
	  return e.getEventBits();
  }

  public abstract Element getFirstChild(Element elem);

  public String getInnerHTML(Element elem) {
	  ElementImpl e = (ElementImpl) elem;
    return GWTContext.getXMLOutputter().outputString(e.getContent());
  }

  public String getInnerText(Element node) { 
    ElementImpl e = (ElementImpl) node;
    return GWTContext.getXMLOutputter().outputString(e.getContent(new ContentFilter(ContentFilter.TEXT)));
  }

  public int getIntAttribute(Element elem, String attr) {
	  return new Integer(getAttribute(elem, attr)).intValue();
  }

  public int getIntStyleAttribute(Element elem, String attr) {
	  // TODO: Implement style handling
    return 0;
  }

  public abstract Element getNextSibling(Element elem);

  public abstract Element getParent(Element elem);

  public String getStyleAttribute(Element elem, String attr) {
    ElementImpl e = (ElementImpl) elem;
    if (e.getAttributeValue("style") != null) {
    	return CSSParser.getStyleAttribute(e.getAttributeValue("style"), attr);
    }
    return null;
  }

  public abstract void init();

  public abstract void insertChild(Element parent, Element child,
      int index);

  public void insertListItem(Element select, String item, String value,
      int index) {
    Element option = DOM.createElement("option");
    DOM.setInnerText(option, item);
    if (value != null) {
      DOM.setAttribute(option, "value", value);
    }
    if (index == -1) {
      DOM.appendChild(select, option);
    } else {
      DOM.insertChild(select, option, index);
    }
  }

  public abstract boolean isOrHasChild(Element parent, Element child);

  public abstract void releaseCapture(Element elem);

  public void removeChild(Element parent, Element child) {
	  ElementImpl p = (ElementImpl) parent;
    ElementImpl c = (ElementImpl) child;
	  p.removeContent(c);	  
  }

  public void scrollIntoView(Element elem) {
	  throw new RuntimeException("Not supported");
  }

  public void setAttribute(Element elem, String attr, String value) {
	  ElementImpl e = (ElementImpl) elem;
	  e.setAttribute(attr, value);
  }
  public void setBooleanAttribute(Element elem, String attr,
      boolean value) {
	  setAttribute(elem, attr, Boolean.toString(value));
  }

  public abstract void setCapture(Element elem);

  public void setEventListener(Element elem,
      EventListener listener) {
	  ((ElementImpl) elem).setEventListener(listener);
  }

  public void setInnerHTML(Element elem, String html) {
	  ElementImpl e = (ElementImpl) elem;
	  List l = HTMLParser.parseHtmlFragment(new StringReader(html));
	  e.setContent(l);
  }

  public void setInnerText(Element elem, String text) {
	  ElementImpl e = (ElementImpl) elem;
	  e.setText(text);
  }

  public void setIntAttribute(Element elem, String attr, int value) {
	  setAttribute(elem, attr, Integer.toString(value));
  }

  public void setIntStyleAttribute(Element elem, String attr, int value) {
	  // TODO: Style handling
  }

  public void setOptionText(Element select, String text, int index) {
	  // TODO
  }

  public void setStyleAttribute(Element elem, String attr,
      String value) {
	    ElementImpl e = (ElementImpl) elem;
	    String oldText = e.getAttributeValue("style");
	    if (oldText == null) {
	    	oldText = "";
	    }
	    String text = CSSParser.setStyleAttribute(oldText, attr, value);
		e.setAttribute("style", text);
	    
  }

  public abstract void sinkEvents(Element elem, int eventBits);

  public abstract String toString(Element elem);
}
